package core;

import infrastructure.exceptions.BaseServerRuntimeException;
import infrastructure.exceptions.PrivilidgesException;
import infrastructure.exceptions.ServerFataError;
import infrastructure.exceptions.operations.OperationException;

import java.io.IOException;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;


import remote.SerializableIterator;
import remote.Session;
import server.IServerSession;
import server.querry.Condition;
import dal.IPet;
import dal.JPAAgent;
import dal.Pet;
import dal.Pet_type;
import dal.User;

public class ServerSession implements IServerSession,Serializable {

	
	private static final long serialVersionUID = 2411869876319862514L;
	
	protected transient Session userSession;

	public ServerSession(Session userSession){
		this.userSession = userSession; 
	}
	
	public Iterator<Pet_type> getAllPetTypes() throws PrivilidgesException {
		
		checkAccess();
		
		try{
			EntityManager entityManager = JPAAgent.getInstance().getEntityManagerFactory().createEntityManager();			
			Query getUserByIdQuerry = entityManager.createQuery("SELECT x FROM pet_types x");			
			List<Pet_type> petsList = getUserByIdQuerry.getResultList();			
			return new SerializableIterator(petsList);
		}catch(Exception ex){
			Logger.getLogger(this.getClass().getName()).logp(Level.SEVERE, this.getClass().getName(), 
					"getAllPetTypes", "Could not get all pet types ",ex);
			throw new ServerFataError(ex.getMessage(),ex);
		}
	}

	public Iterator<IPet> getAllPets() throws PrivilidgesException {
		checkAccess();
		
		try{
			EntityManager entityManager = JPAAgent.getInstance().getEntityManagerFactory().createEntityManager();			
			Query getUserByIdQuerry = entityManager.createQuery("SELECT x FROM pets x");			
			List<Pet> petsList = getUserByIdQuerry.getResultList();			
			return new SerializableIterator(petsList);
		}catch(Exception ex){
			Logger.getLogger(this.getClass().getName()).logp(Level.SEVERE, this.getClass().getName(), 
					"getAllPets", "Could not get all pets ",ex);
			throw new ServerFataError(ex.getMessage(),ex);
		}
	}

	public Iterator<IPet> getPets(Condition querry) throws PrivilidgesException {
		
		checkAccess();
		
		try{
			EntityManager entityManager = JPAAgent.getInstance().getEntityManagerFactory().createEntityManager();			
			Query conditionQuerry = entityManager.createQuery(querry.getQuerry());			
			List<Pet> petsList = conditionQuerry.getResultList();			
			return new SerializableIterator(petsList);
		}catch(Exception ex){
			Logger.getLogger(this.getClass().getName()).logp(Level.SEVERE, this.getClass().getName(), 
					"getAllPets", "Could not get all pets ",ex);
			throw new ServerFataError(ex.getMessage(),ex);
		}
	}

	public void giveForAdoption(IPet petToDeliver) throws OperationException,PrivilidgesException {
		checkAccess();
		
		Pet deliveredPet = (Pet)petToDeliver;
		EntityManager entityManager = JPAAgent.getInstance().getEntityManagerFactory().createEntityManager();
		EntityTransaction transaction = entityManager.getTransaction();
		
		try{
			transaction.begin();
			User deliverUser = entityManager.find(User.class,this.userSession.getUser().getId());
			Pet_type petType = entityManager.find(Pet_type.class, deliveredPet.getPet_type().getType_id()); 
			deliveredPet.setDeliverUser(deliverUser);
			deliveredPet.setPetType(petType);
			entityManager.persist(deliveredPet);
			transaction.commit();
		}catch(Exception ex){
			if (transaction.isActive()){
				transaction.rollback();
			}
			Logger.getLogger(this.getClass().getName()).logp(Level.WARNING, this.getClass().getName(), 
					"giveForAdoption", "Could not deliver pet  '" + deliveredPet + "'",ex);			
			throw new OperationException("failed to deliver pet");
			
		}
	}

	public void removePetFromSystem(Integer  id) throws OperationException,PrivilidgesException {
		checkAccess();
		
		EntityManager entityManager = JPAAgent.getInstance().getEntityManagerFactory().createEntityManager();
		EntityTransaction transaction = entityManager.getTransaction();			
					
		transaction.begin();
		
		try{			
			// get pet to remove
			Pet reconnectedPet = entityManager.find(Pet.class,id);
			
			if (null != reconnectedPet){
				
				// check if the deliver user is this user 
				if (isPetRemoveAllowed(reconnectedPet,userSession.getUser())){
					entityManager.remove(reconnectedPet);
					transaction.commit();
				}else{
					transaction.rollback();
					throw new OperationException("Pet is not delivered by user who owns the pet '" 
							+ "Deliver user '" + reconnectedPet.getDeliver_user() + "' operation user '" + 
							this.userSession.getUser());
				}
			}
			
		}catch(Exception ex){
			if (transaction.isActive()){
				transaction.rollback();
			}
			
			Logger.getLogger(this.getClass().getName()).logp(Level.WARNING, this.getClass().getName(), 
					"removePetFromSystem", "Could not remove pet'" ,ex);
			throw new OperationException("failed to remove pet pet");
		}
		
	}
	
	public byte[] getPetPicture(Integer petId){
		checkAccess();
		
		try{
			EntityManager entityManager = JPAAgent.getInstance().getEntityManagerFactory().createEntityManager();
			Pet pet = entityManager.find(Pet.class,petId);
			return pet.getPicture();			
		}catch(Exception ex){
			Logger.getLogger(this.getClass().getName()).logp(Level.SEVERE, this.getClass().getName(), 
					"getAllPets", "Could not get all pets ",ex);
			throw new ServerFataError(ex.getMessage(),ex);
		}
	}
	
	
	/**
	 * Pet removal is allowed if the user is an administrator user (in which case it can remove whatever pet it wants)
	 * or if the user is the user that delivered the pet
	 * @param petToRemote - pet to remove from the database
	 * @param loggedInUser  - user that performs the operation
	 * @return whether the pet removal is allowed 
	 */
	private boolean isPetRemoveAllowed(Pet petToRemote,User loggedInUser){
		return ( (loggedInUser.isAdministrator()) || 
				 (petToRemote.getDeliver_user().equals(loggedInUser)) );
	}
	
	private void writeObject(java.io.ObjectOutputStream out) throws IOException{
		out.writeLong(userSession.getSessionID().toLong());
		out.writeObject(userSession.getUser());
	}
	
	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
		throw new IOException(new ServerFataError("This class should never be deserialized on server .. bug"));		
	}

	public long getSessionId() {
		return userSession.getSessionID().toLong();
	}

	/**
	 * 
	 * @throws PrivilidgesException - in case user is a gauest
	 */
	protected void checkAccess() throws PrivilidgesException{
		if (this.userSession.isGuest()){
			throw new PrivilidgesException();
		}
	}
}
